/*
 * Copyright (c) 2025, sakumisu
 *
 * SPDX-License-Identifier: Apache-2.0
 */

//Refer to https://github.com/espressif/esp-bsp/blob/master/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c

#include "usbh_core.h"
#include "usbh_hid.h"
#include "usbh_hid_lvgl.h"

/* LVGL image of cursor */
LV_IMG_DECLARE(img_cursor)

struct usbh_hid_lvgl {
    struct {
        lv_indev_t *indev;   /* LVGL mouse input device driver */
        uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */
        int16_t x;           /* Mouse X coordinate */
        int16_t y;           /* Mouse Y coordinate */
        bool left_button;    /* Mouse left button state */
    } mouse;
    struct {
        lv_indev_t *indev; /* LVGL keyboard input device driver */
        uint32_t last_key;
        bool pressed;
    } kb;
};

static struct usbh_hid_lvgl g_hid_lvgl;

USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t hid_mouse_buffer[64];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t hid_keyboard_buffer[64];

static void usbh_hid_lvgl_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
    int16_t width = 0;
    int16_t height = 0;
    struct usbh_hid_lvgl *ctx = &g_hid_lvgl;

    lv_display_t *disp = lv_indev_get_display(indev_drv);
    if (lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_0 || lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_180) {
        width = lv_display_get_physical_horizontal_resolution(disp);
        height = lv_display_get_vertical_resolution(disp);
    } else {
        width = lv_display_get_vertical_resolution(disp);
        height = lv_display_get_physical_horizontal_resolution(disp);
    }

    /* Screen borders */
    if (ctx->mouse.x < 0) {
        ctx->mouse.x = 0;
    } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) {
        ctx->mouse.x = width * ctx->mouse.sensitivity;
    }
    if (ctx->mouse.y < 0) {
        ctx->mouse.y = 0;
    } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) {
        ctx->mouse.y = height * ctx->mouse.sensitivity;
    }

    /* Get coordinates by rotation with sensitivity */
    switch (lv_display_get_rotation(disp)) {
        case LV_DISPLAY_ROTATION_0:
            data->point.x = ctx->mouse.x / ctx->mouse.sensitivity;
            data->point.y = ctx->mouse.y / ctx->mouse.sensitivity;
            break;
        case LV_DISPLAY_ROTATION_90:
            data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity;
            data->point.x = ctx->mouse.y / ctx->mouse.sensitivity;
            break;
        case LV_DISPLAY_ROTATION_180:
            data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity;
            data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity;
            break;
        case LV_DISPLAY_ROTATION_270:
            data->point.y = ctx->mouse.x / ctx->mouse.sensitivity;
            data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity;
            break;
    }

    if (ctx->mouse.left_button) {
        data->state = LV_INDEV_STATE_PRESSED;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

static void usbh_hid_lvgl_read_keyboard(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
    struct usbh_hid_lvgl *ctx = &g_hid_lvgl;

    data->key = ctx->kb.last_key;
    if (ctx->kb.pressed) {
        data->state = LV_INDEV_STATE_PRESSED;
        ctx->kb.pressed = false;
    } else {
        data->state = LV_INDEV_STATE_RELEASED;
        ctx->kb.last_key = 0;
    }
}

lv_indev_t *usbh_hid_lvgl_add_mouse(uint8_t sensitivity)
{
    lv_indev_t *indev;

    /* Initialize USB HID */
    struct usbh_hid_lvgl *hid_ctx = &g_hid_lvgl;

    /* Mouse sensitivity cannot be zero */
    hid_ctx->mouse.sensitivity = (sensitivity == 0 ? 1 : sensitivity);

    int32_t ver_res = lv_display_get_vertical_resolution(lv_display_get_default());
    int32_t hor_res = lv_display_get_physical_horizontal_resolution(lv_display_get_default());

    /* Default coordinates to screen center */
    hid_ctx->mouse.x = (hor_res * hid_ctx->mouse.sensitivity) / 2;
    hid_ctx->mouse.y = (ver_res * hid_ctx->mouse.sensitivity) / 2;

    /* Register a mouse input device */
    indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev, usbh_hid_lvgl_read_mouse);
    lv_indev_set_driver_data(indev, hid_ctx);
    hid_ctx->mouse.indev = indev;

    /* Set image of cursor */
    lv_obj_t *cursor;
    cursor = lv_img_create(lv_scr_act());
    lv_img_set_src(cursor, &img_cursor);
    lv_indev_set_cursor(indev, cursor);

    return indev;
}

lv_indev_t *usbh_hid_lvgl_add_keyboard(void)
{
    lv_indev_t *indev;

    /* Initialize USB HID */
    struct usbh_hid_lvgl *hid_ctx = &g_hid_lvgl;

    /* Register a keyboard input device */
    indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
    lv_indev_set_read_cb(indev, usbh_hid_lvgl_read_keyboard);
    lv_indev_set_driver_data(indev, hid_ctx);
    hid_ctx->kb.indev = indev;

    return indev;
}

void usbh_hid_mouse_callback(void *arg, int nbytes)
{
    struct usbh_hid *hid_class = (struct usbh_hid *)arg;

    if (nbytes > 0) {
        struct usbh_hid_lvgl *hid_ctx = &g_hid_lvgl;

        hid_ctx->mouse.left_button = hid_mouse_buffer[0];
        hid_ctx->mouse.x += (int8_t)hid_mouse_buffer[1];
        hid_ctx->mouse.y += (int8_t)hid_mouse_buffer[2];

        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_mouse_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_mouse_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else if (nbytes == -USB_ERR_NAK) { /* only dwc2 should do this */
        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_mouse_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_mouse_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else {
    }
}

static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift)
{
    char ret_key = 0;

    const uint8_t keycode2ascii[57][2] = {
        { 0, 0 },       /* HID_KEY_NO_PRESS        */
        { 0, 0 },       /* HID_KEY_ROLLOVER        */
        { 0, 0 },       /* HID_KEY_POST_FAIL       */
        { 0, 0 },       /* HID_KEY_ERROR_UNDEFINED */
        { 'a', 'A' },   /* HID_KEY_A               */
        { 'b', 'B' },   /* HID_KEY_B               */
        { 'c', 'C' },   /* HID_KEY_C               */
        { 'd', 'D' },   /* HID_KEY_D               */
        { 'e', 'E' },   /* HID_KEY_E               */
        { 'f', 'F' },   /* HID_KEY_F               */
        { 'g', 'G' },   /* HID_KEY_G               */
        { 'h', 'H' },   /* HID_KEY_H               */
        { 'i', 'I' },   /* HID_KEY_I               */
        { 'j', 'J' },   /* HID_KEY_J               */
        { 'k', 'K' },   /* HID_KEY_K               */
        { 'l', 'L' },   /* HID_KEY_L               */
        { 'm', 'M' },   /* HID_KEY_M               */
        { 'n', 'N' },   /* HID_KEY_N               */
        { 'o', 'O' },   /* HID_KEY_O               */
        { 'p', 'P' },   /* HID_KEY_P               */
        { 'q', 'Q' },   /* HID_KEY_Q               */
        { 'r', 'R' },   /* HID_KEY_R               */
        { 's', 'S' },   /* HID_KEY_S               */
        { 't', 'T' },   /* HID_KEY_T               */
        { 'u', 'U' },   /* HID_KEY_U               */
        { 'v', 'V' },   /* HID_KEY_V               */
        { 'w', 'W' },   /* HID_KEY_W               */
        { 'x', 'X' },   /* HID_KEY_X               */
        { 'y', 'Y' },   /* HID_KEY_Y               */
        { 'z', 'Z' },   /* HID_KEY_Z               */
        { '1', '!' },   /* HID_KEY_1               */
        { '2', '@' },   /* HID_KEY_2               */
        { '3', '#' },   /* HID_KEY_3               */
        { '4', '$' },   /* HID_KEY_4               */
        { '5', '%' },   /* HID_KEY_5               */
        { '6', '^' },   /* HID_KEY_6               */
        { '7', '&' },   /* HID_KEY_7               */
        { '8', '*' },   /* HID_KEY_8               */
        { '9', '(' },   /* HID_KEY_9               */
        { '0', ')' },   /* HID_KEY_0               */
        { '\r', '\r' }, /* HID_KEY_ENTER           */
        { 0, 0 },       /* HID_KEY_ESC             */
        { '\b', 0 },    /* HID_KEY_DEL             */
        { 0, 0 },       /* HID_KEY_TAB             */
        { ' ', ' ' },   /* HID_KEY_SPACE           */
        { '-', '_' },   /* HID_KEY_MINUS           */
        { '=', '+' },   /* HID_KEY_EQUAL           */
        { '[', '{' },   /* HID_KEY_OPEN_BRACKET    */
        { ']', '}' },   /* HID_KEY_CLOSE_BRACKET   */
        { '\\', '|' },  /* HID_KEY_BACK_SLASH      */
        { '\\', '|' },
        /* HID_KEY_SHARP           */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
        { ';', ':' },                 /* HID_KEY_COLON           */
        { '\'', '"' },                /* HID_KEY_QUOTE           */
        { '`', '~' },                 /* HID_KEY_TILDE           */
        { ',', '<' },                 /* HID_KEY_LESS            */
        { '.', '>' },                 /* HID_KEY_GREATER         */
        { '/', '?' }                  /* HID_KEY_SLASH           */
    };

    if (shift > 1) {
        shift = 1;
    }

    if ((key >= HID_KBD_USAGE_A) && (key <= HID_KBD_USAGE_QUESTION)) {
        ret_key = keycode2ascii[key][shift];
    }

    return ret_key;
}

void usbh_hid_keyboard_callback(void *arg, int nbytes)
{
    struct usbh_hid *hid_class = (struct usbh_hid *)arg;

    if (nbytes > 0) {
        struct usbh_hid_lvgl *hid_ctx = &g_hid_lvgl;
        struct usb_hid_kbd_report *keyboard = (struct usb_hid_kbd_report *)hid_keyboard_buffer;

        for (int i = 0; i < 6; i++) {
            if (keyboard->key[i] > HID_KBD_USAGE_MAX) {
                char key = 0;

                /* LVGL special keys */
                if (keyboard->key[i] == HID_KBD_USAGE_TAB) {
                    if (((keyboard->modifier & HID_MODIFER_LSHIFT) || (keyboard->modifier & HID_MODIFER_RSHIFT))) {
                        key = LV_KEY_PREV;
                    } else {
                        key = LV_KEY_NEXT;
                    }
                } else if (keyboard->key[i] == HID_KBD_USAGE_RIGHT) {
                    key = LV_KEY_RIGHT;
                } else if (keyboard->key[i] == HID_KBD_USAGE_LEFT) {
                    key = LV_KEY_LEFT;
                } else if (keyboard->key[i] == HID_KBD_USAGE_DOWN) {
                    key = LV_KEY_DOWN;
                } else if (keyboard->key[i] == HID_KBD_USAGE_UP) {
                    key = LV_KEY_UP;
                } else if (keyboard->key[i] == HID_KBD_USAGE_ENTER || keyboard->key[i] == HID_KBD_USAGE_KPDEMTER) {
                    key = LV_KEY_ENTER;
                } else if (keyboard->key[i] == HID_KBD_USAGE_DELETE) {
                    key = LV_KEY_DEL;
                } else if (keyboard->key[i] == HID_KBD_USAGE_HOME) {
                    key = LV_KEY_HOME;
                } else if (keyboard->key[i] == HID_KBD_USAGE_END) {
                    key = LV_KEY_END;
                } else {
                    /* Get ASCII char */
                    key = usb_hid_get_keyboard_char(keyboard->key[i], ((keyboard->modifier & HID_MODIFER_LSHIFT) || (keyboard->modifier & HID_MODIFER_RSHIFT)));
                }

                if (key == 0) {
                    USB_LOG_ERR("Not recognized key: %c (%d)\r\n", keyboard->key[i], keyboard->key[i]);
                }
                hid_ctx->kb.last_key = key;
                hid_ctx->kb.pressed = true;
            }
        }

        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_keyboard_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_mouse_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else if (nbytes == -USB_ERR_NAK) { /* only dwc2 should do this */
        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_keyboard_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_mouse_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else {
    }
}

void usbh_hid_run(struct usbh_hid *hid_class)
{
    usbh_hid_set_protocol(hid_class, 0);

    if (hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol == HID_PROTOCOL_KEYBOARD) {
        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_keyboard_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_keyboard_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else if (hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol == HID_PROTOCOL_MOUSE) {
        usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_mouse_buffer, hid_class->intin->wMaxPacketSize, 0,
                          usbh_hid_mouse_callback, hid_class);
        usbh_submit_urb(&hid_class->intin_urb);
    } else {
    }
}

void usbh_hid_stop(struct usbh_hid *hid_class)
{
}